﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using TSU.FAMC.ComputerEngineering.Classes.Circuits.Basis;
using TSU.FAMC.ComputerEngineering.Classes.Enums;

namespace TSU.FAMC.ComputerEngineering.Classes.Circuits.Readers
{
    /// <summary>
    /// Reads circuit from simple text gate format: INPUT(g0)\nOUTPUT(g2)\ng2 = NOT(g1)
    /// </summary>
    public class CircuitReaderPlain : CircuitReader
    {
        // Properties

        /// <summary>
        /// Input string to read from.
        /// </summary>
        public string Input { get; set; }


        // Methods

        /// <summary>
        /// Default constructor.
        /// </summary>
        public CircuitReaderPlain()
        {
            Input = String.Empty;
            GatesCount = 0;
        }

        /// <summary>
        /// Reads circuit from simple text format.
        /// </summary>
        public override void Read()
        {
            if (Input == string.Empty)
            {
                throw new InvalidOperationException("Input string required.");
            }

            // splitting to lines
            var delimeters = new[] { "\n", "\r\n" };
            var lines = Input.Split(delimeters, Int32.MaxValue, StringSplitOptions.RemoveEmptyEntries);
            if (lines.Length == 0)
            {
                throw new InvalidDataException("Invalid source string: no one line of values.");
            }

            // instanciating circuit object
            Circuit = new Circuit();
            GatesCount = 0;

            var gatesDict = new Dictionary<string, Element>();
            Elements = new List<Element>();

            // parsing lines
            var gatePattern = new Regex("([a-z_]+[0-9.a-z_]*)\\s*=\\s*(NOT|AND|OR|NAND|NOR|0|1|DFF)\\(([0-9., a-z_]+)\\)", RegexOptions.IgnoreCase);
            var ioPattern = new Regex("(INPUT|OUTPUT)\\s*\\(\\s*([a-z_]+[0-9.a-z_]*)\\s*\\)", RegexOptions.IgnoreCase);
            var fictivePattern = new Regex("([a-z_]+[0-9.a-z_]*)\\s*=\\s*([a-z_]+[0-9.a-z_]*)", RegexOptions.IgnoreCase);
            var constantPattern = new Regex("([a-z_]+[0-9.a-z_]*)\\s*=\\s*(0|1)", RegexOptions.IgnoreCase);
            foreach (var line in lines)
            {
                if (line[0] == '#')
                {
                    // skipping comments
                    continue;
                }

                // checking gate pattern
                var match = gatePattern.Match(line);
                if (match.Success)
                {
                    // gate
                    var masterGateNumber = match.Groups[1].ToString();
                    var gateTypeString = match.Groups[2].ToString().ToUpper();
                    var slaveGates = match.Groups[3].ToString().Trim();

                    // determing type of the gate
                    GateType gateType;
                    if (gateTypeString == "NOT")
                    {
                        gateType = GateType.Not;
                    }
                    else if (gateTypeString == "AND")
                    {
                        gateType = GateType.And;
                    }
                    else if (gateTypeString == "OR")
                    {
                        gateType = GateType.Or;
                    }
                    else if (gateTypeString == "NAND")
                    {
                        gateType = GateType.Nand;
                    }
                    else if (gateTypeString == "NOR")
                    {
                        gateType = GateType.Nor;
                    }
                    else if (gateTypeString == "0")
                    {
                        gateType = GateType.Constant0;
                    }
                    else if (gateTypeString == "1")
                    {
                        gateType = GateType.Constant1;
                    }
                    else if (gateTypeString == "DFF")
                    {
                        gateType = GateType.Dff;
                    }
                    else
                    {
                        throw new InvalidDataException(String.Format("Unknown gate type {0}", gateTypeString));
                    }

                    Element masterGate;
                    if (gatesDict.ContainsKey(masterGateNumber))
                    {
                        // master gate has been created already
                        masterGate = gatesDict[masterGateNumber];
                        if (!(masterGate is StubGate))
                        {
                            // master gate has been declared already
                            throw new InvalidDataException(String.Format("Could not create gate with number {0} because it has been already created", masterGateNumber));
                        }

                        // determing gate
                        var gate = GateFactory.Create(gateType);
                        gate.Name = masterGateNumber;

                        // copying inputs and outputs
                        foreach (var input in masterGate.Inputs)
                        {
                            gate.Inputs.Add(input);
                        }
                        foreach (var output in masterGate.Outputs)
                        {
                            gate.Outputs.Add(output);
                        }
                        masterGate.Inputs.Clear();
                        masterGate.Outputs.Clear();

                        // updating input and output pins
                        foreach (var pin in gate.Inputs)
                        {
                            pin.Outputs.Remove(masterGate);
                            pin.Outputs.Add(gate);
                        }
                        foreach (var pin in gate.Outputs)
                        {
                            pin.Inputs.Remove(masterGate);
                            pin.Inputs.Add(gate);
                        }

                        // updating circuit interfaces
                        var index = Circuit.Inputs.IndexOf(masterGate);
                        if (index >= 0)
                        {
                            Circuit.Inputs.Insert(index, gate);
                            Circuit.Inputs.Remove(masterGate);
                        }

                        index = Circuit.Outputs.IndexOf(masterGate);
                        if (index >= 0)
                        {
                            Circuit.Outputs.Insert(index, gate);
                            Circuit.Outputs.Remove(masterGate);
                        }

                        // updating gates dict
                        gatesDict.Remove(masterGateNumber);
                        gatesDict.Add(masterGateNumber, gate);

                        masterGate = gate;
                    }
                    else
                    {
                        // creating new gate
                        masterGate = GateFactory.Create(gateType);
                        masterGate.Name = masterGateNumber;

                        // memorizing gate
                        gatesDict.Add(masterGateNumber, masterGate);
                    }

                    // parsing slave gates
                    var dlmtrs = new[] { ",", ", " };
                    var slaves = slaveGates.Split(dlmtrs, Int32.MaxValue, StringSplitOptions.RemoveEmptyEntries);

                    var gateNumberPattern = new Regex("[a-z_]+[0-9.a-z_]*", RegexOptions.IgnoreCase);
                    foreach (var slaveGateStr in slaves)
                    {
                        var slaveGateStrTrimmed = slaveGateStr.Trim();
                        var slaveMatch = gateNumberPattern.Match(slaveGateStrTrimmed);
                        if (!slaveMatch.Success)
                        {
                            // invalid slave number
                            throw new InvalidDataException(String.Format("Invalid input gate name {0}", slaveGateStrTrimmed));
                        }

                        var slaveGateNumber = slaveMatch.Groups[0].ToString();

                        Element slaveGate;
                        if (gatesDict.ContainsKey(slaveGateNumber))
                        {
                            // slave gate has been created already
                            slaveGate = gatesDict[slaveGateNumber];
                        }
                        else
                        {
                            // creating fictive slave gate
                            slaveGate = new StubGate { Name = slaveGateNumber };

                            // memorizing gate
                            gatesDict.Add(slaveGateNumber, slaveGate);
                        }

                        // connecting master and slave gates
                        masterGate.Inputs.Add(slaveGate);

                        if (slaveGate.Outputs.Count == 0)
                        {
                            slaveGate.Outputs.Add(masterGate);
                        }
                        else
                        {
                            // creating joint for multioutput relation
                            var joint = new Joint();
                            joint.Inputs.Add(slaveGate);
                            joint.Outputs.Add(slaveGate.Outputs[0]);
                            joint.Outputs.Add(masterGate);

                            // updating references
                            var index = masterGate.Inputs.IndexOf(slaveGate);
                            masterGate.Inputs.Remove(slaveGate);
                            masterGate.Inputs.Insert(index, joint);

                            index = slaveGate.Outputs[0].Inputs.IndexOf(slaveGate);
                            slaveGate.Outputs[0].Inputs.Remove(slaveGate);
                            slaveGate.Outputs[0].Inputs.Insert(index, joint);

                            slaveGate.Outputs.Clear();
                            slaveGate.Outputs.Add(joint);

                            // memorizing new element
                            Elements.Add(joint);
                        }
                    }

                    continue;
                }

                
                // checking IO pattern
                match = ioPattern.Match(line);
                if (match.Success)
                {
                    // input or output gate description line
                    var gateType = match.Groups[1].ToString();
                    var gateNumber = match.Groups[2].ToString();

                    Element gate;
                    if (gatesDict.ContainsKey(gateNumber))
                    {
                        // gate has been created already
                        gate = gatesDict[gateNumber];
                    }
                    else
                    {
                        // creating fictive input or output
                        gate = new StubGate { Name = gateNumber };

                        // memorizing gate
                        gatesDict.Add(gateNumber, gate);
                    }

                    // registering gate in the interface
                    if (gateType == "INPUT")
                    {
                        Circuit.Inputs.Add(gate);
                    }
                    else if (gateType == "OUTPUT")
                    {
                        var circuitPin = new Link();
                        circuitPin.Inputs.Add(gate);
                        gate.Outputs.Add(circuitPin);
                        Circuit.Outputs.Add(circuitPin);

                        // memorizing new element
                        Elements.Add(circuitPin);
                    }

                    continue;
                }

                
                // checking fictive pattern
                match = fictivePattern.Match(line);
                if (match.Success)
                {
                    // fictive gate (simple stub)
                    var masterGateNumber = match.Groups[1].ToString();
                    var slaveGateNumber = match.Groups[2].ToString();

                    Element slaveGate;
                    if (gatesDict.ContainsKey(slaveGateNumber))
                    {
                        // slave gate has been created already
                        slaveGate = gatesDict[slaveGateNumber];
                    }
                    else
                    {
                        // creating fictive slave gate
                        slaveGate = new StubGate { Name = slaveGateNumber };

                        // memorizing gate
                        gatesDict.Add(slaveGateNumber, slaveGate);
                    }

                    Element masterGate;
                    if (gatesDict.ContainsKey(masterGateNumber))
                    {
                        // master gate has been created already
                        masterGate = gatesDict[masterGateNumber];

                        if (!(masterGate is StubGate))
                        {
                            // master gate has been declared already
                            throw new InvalidDataException(String.Format("Could not create gate with number {0} because it has been already created", masterGateNumber));
                        }

                        // determing fictive gate as a stub gate
                        var stub = new StubGate { Name = masterGateNumber };

                        // copying inputs and outputs
                        foreach (var input in masterGate.Inputs)
                        {
                            stub.Inputs.Add(input);
                        }
                        foreach (var output in masterGate.Outputs)
                        {
                            stub.Outputs.Add(output);
                        }
                        masterGate.Outputs.Clear();
                        masterGate.Inputs.Clear();

                        // updating input and output pins
                        foreach (var pin in stub.Inputs)
                        {
                            pin.Outputs.Remove(masterGate);
                            pin.Outputs.Add(stub);
                        }
                        foreach (var pin in stub.Outputs)
                        {
                            pin.Inputs.Remove(masterGate);
                            pin.Inputs.Add(stub);
                        }

                        // updating circuit interfaces
                        var index = Circuit.Inputs.IndexOf(masterGate);
                        if (index >= 0)
                        {
                            Circuit.Inputs.Insert(index, stub);
                            Circuit.Inputs.Remove(masterGate);
                        }

                        index = Circuit.Outputs.IndexOf(masterGate);
                        if (index >= 0)
                        {
                            Circuit.Outputs.Insert(index, stub);
                            Circuit.Outputs.Remove(masterGate);
                        }

                        // updating gates dict
                        gatesDict.Remove(masterGateNumber);
                        gatesDict.Add(masterGateNumber, stub);

                        masterGate = stub;
                    }
                    else
                    {
                        // creating fictive gate (stub)
                        masterGate = new Link { Name = masterGateNumber };

                        // memorizing gate
                        gatesDict.Add(masterGateNumber, masterGate);
                    }

                    // connecting master and a slave
                    masterGate.Inputs.Add(slaveGate);

                    if (slaveGate.Outputs.Count == 0)
                    {
                        slaveGate.Outputs.Add(masterGate);
                    }
                    else
                    {
                        // creating joint for multioutput relation
                        var joint = new Joint();
                        joint.Inputs.Add(slaveGate);
                        joint.Outputs.Add(slaveGate.Outputs[0]);
                        joint.Outputs.Add(masterGate);

                        // updating references
                        var index = masterGate.Inputs.IndexOf(slaveGate);
                        masterGate.Inputs.Remove(slaveGate);
                        masterGate.Inputs.Insert(index, joint);

                        index = slaveGate.Outputs[0].Inputs.IndexOf(slaveGate);
                        slaveGate.Outputs[0].Inputs.Remove(slaveGate);
                        slaveGate.Outputs[0].Inputs.Insert(index, joint);

                        slaveGate.Outputs.Clear();
                        slaveGate.Outputs.Add(joint);

                        // memorizing new element
                        Elements.Add(joint);
                    }

                    continue;
                }


                // checking constant pattern
                match = constantPattern.Match(line);
                if (match.Success)
                {
                    // constant gate
                    var masterGateNumber = match.Groups[1].ToString();
                    var constantType = match.Groups[2].ToString();

                    Element masterGate;
                    if (gatesDict.ContainsKey(masterGateNumber))
                    {
                        // master gate has been created already
                        masterGate = gatesDict[masterGateNumber];

                        if (!(masterGate is StubGate))
                        {
                            // master gate has been declared already
                            throw new InvalidDataException(String.Format("Could not create gate with number {0} because it has been already created", masterGateNumber));
                        }

                        // determing gate as a constant
                        Gate gate;
                        if (constantType == "0")
                        {
                            gate = new Constant0Gate { Name = masterGateNumber };
                        }
                        else if (constantType == "1")
                        {
                            gate = new Constant1Gate { Name = masterGateNumber };
                        }
                        else
                        {
                            throw new InvalidDataException("There are only two types of constants: 0 and 1");
                        }

                        // updating pins

                        // copying inputs and outputs
                        foreach (var input in masterGate.Inputs)
                        {
                            gate.Inputs.Add(input);
                        }
                        foreach (var output in masterGate.Outputs)
                        {
                            gate.Outputs.Add(output);
                        }
                        masterGate.Outputs.Clear();
                        masterGate.Inputs.Clear();


                        foreach (var pin in gate.Outputs)
                        {
                            pin.Inputs.Remove(masterGate);
                            pin.Inputs.Add(gate);
                        }

                        // updating circuit interfaces
                        var index = Circuit.Inputs.IndexOf(masterGate);
                        if (index >= 0)
                        {
                            Circuit.Inputs.Insert(index, gate);
                            Circuit.Inputs.Remove(masterGate);
                        }

                        index = Circuit.Outputs.IndexOf(masterGate);
                        if (index >= 0)
                        {
                            Circuit.Outputs.Insert(index, gate);
                            Circuit.Outputs.Remove(masterGate);
                        }

                        // updating gates dict
                        gatesDict.Remove(masterGateNumber);
                        gatesDict.Add(masterGateNumber, gate);
                    }
                    else
                    {
                        // creating constant gate
                        if (constantType == "0")
                        {
                            masterGate = new Constant0Gate { Name = masterGateNumber };
                        }
                        else if (constantType == "1")
                        {
                            masterGate = new Constant1Gate { Name = masterGateNumber };
                        }
                        else
                        {
                            throw new InvalidDataException("There are only two types of constants allowed: 0 and 1");
                        }

                        // memorizing gate
                        gatesDict.Add(masterGateNumber, masterGate);
                    }

                    continue;
                }
            }

            // setting result properties
            GatesCount = gatesDict.Count;

            // appending gates to elements list
            Elements.AddRange(gatesDict.Values.ToList());
        }
    }
}
